/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.modules.i18n;
import java.util.ResourceBundle;
import java.io.IOException;
import java.beans.Introspector;
import java.awt.BorderLayout;
import java.awt.event.*;
import java.awt.Dialog;
import javax.swing.text.StyledDocument;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.text.Caret;
import javax.swing.KeyStroke;
import javax.swing.text.JTextComponent;
import javax.swing.text.BadLocationException;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.actions.CookieAction;
import org.openide.cookies.SourceCookie;
import org.openide.cookies.EditorCookie;
import org.openide.nodes.Node;
import org.openide.loaders.DataObject;
import org.openide.NotifyDescriptor;
import org.openide.DialogDescriptor;
import org.openide.TopManager;
import org.netbeans.editor.BaseDocument;
import org.netbeans.editor.GuardedDocument;
import org.netbeans.editor.BaseCaret;
import org.netbeans.editor.Utilities;
import org.netbeans.modules.properties.ResourceBundleStringEditor;
import org.netbeans.modules.properties.ResourceBundleString;
import org.netbeans.modules.properties.PropertiesDataObject;
import org.netbeans.modules.properties.Util;
import org.netbeans.modules.form.RADComponent;
import org.netbeans.modules.form.FormDataObject;
/**
* Localize support class. Dependent on the editor module and the form module.
*
* @author Petr Jiricka
*/
public class LocalizeSupport {
public LocalizeSupport() {}
public static final String LOCALIZE_FINDER_PROP = "org.netbeans.modules.i18n.LOCALIZE_FINDER";
private static LocalizeSupport instance;
private LocalizeInfo locInfoInstance;
private Dialog localizeDialog;
private LocalizePanel localizePanel;
private JEditorPane currentComponent;
private EditorCookie editCook;
private ResourceBundleStringEditor strEdit = new ResourceBundleStringEditor();
protected BaseDocument myDoc;
protected DataObject obj;
public static LocalizeSupport getLocalizeSupport() {
if (instance == null)
instance = new LocalizeSupport();
return instance;
}
public void localize(BaseDocument doc, DataObject obj) {
this.obj = obj;
if (this.myDoc != doc) {
this.myDoc = doc;
reset();
// initialize the component
editCook = (EditorCookie)obj.getCookie(EditorCookie.class);
if (editCook == null)
return; // PENDING
JEditorPane[] panes = editCook.getOpenedPanes();
if (panes.length == 0) {
NotifyDescriptor.Message message = new NotifyDescriptor.Message(NbBundle.getBundle(LocalizeSupport.class).
getString("MSG_CouldNotOpen"), NotifyDescriptor.ERROR_MESSAGE);
TopManager.getDefault().notify(message);
return;
}
currentComponent = panes[0];
currentComponent.getCaret().setDot(0);
// initializes the finder
getLocalizeFinder(doc).initialize();
// do the search
if (find(-1)) {
showDialog();
fillDialogValues();
}
else {
NotifyDescriptor.Message message = new NotifyDescriptor.Message(NbBundle.getBundle(LocalizeSupport.class).
getString("MSG_NoLocalizableString"), NotifyDescriptor.ERROR_MESSAGE);
TopManager.getDefault().notify(message);
}
}
}
/** Ensures that the component has not been closed.
* @param position position to set the cursor to. If position == -1, sets to the cursor position in
* the previously used component.
* Returns true if the component was validated. */
private boolean ensureComponentValid(int position) {
JEditorPane[] panes = editCook.getOpenedPanes();
if (panes.length == 0)
return false;
// try the ones which are open now
for (int i=0; i < panes.length; i++) {
if (panes[i] == currentComponent) {
currentComponent.requestFocus();
if (position != -1)
currentComponent.getCaret().setDot(position);
return true;
}
}
// not found
int dot = (position != -1) ?
position : // case 1
((currentComponent == null) ? // case 2
0 : // case 2a
currentComponent.getCaret().getDot()); // case 2b
currentComponent = panes[0];
currentComponent.getCaret().setDot(dot);
return true;
}
private LocalizeInfo getLocalizeInfo() {
if (locInfoInstance == null)
locInfoInstance = new LocalizeInfo();
return locInfoInstance;
}
private void reset() {
locInfoInstance = null;
currentComponent = null;
hideDialog();
}
/* Replace button handler. */
private void doReplace() {
ResourceBundleString str = null;
try {
str = (ResourceBundleString)localizePanel.getPropertyValue();
}
catch (IllegalStateException e) {
NotifyDescriptor.Message nd = new NotifyDescriptor.Message(NbBundle.getBundle(LocalizeSupport.class).
getString("EXC_BadKey"), NotifyDescriptor.ERROR_MESSAGE);
TopManager.getDefault().notify(nd);
return;
}
int position = getLocalizeInfo().getPosition();
int len = getLocalizeInfo().getLength();
if (str instanceof ResourceBundleStringForm) {
// form
replaceInForm(getComponent(getLocalizeInfo().getComponentName()),
getLocalizeInfo().getPropertyName(), (ResourceBundleStringForm)str);
}
else {
// no form
strEdit.setValue(str);
strEdit.setClassName(obj.getName());
try {
replaceDirect(position, len, strEdit.getJavaInitializationString());
}
catch (BadLocationException e) {
throw new InternalError();
}
}
if (find(position)) {
showDialog();
fillDialogValues();
}
else
doCancel();
}
/* Replace All button handler. */
private void doReplaceAll() {
// pending
}
/* Skip button handler. */
private void doSkip() {
if (find(-1)) {
showDialog();
fillDialogValues();
}
else
doCancel();
}
/* Cancel button handler. */
private void doCancel() {
hideDialog();
myDoc = null;
}
private boolean replaceDirect(int startPos, int len, String replaceWith) throws BadLocationException {
if (ensureComponentValid(startPos)) {
Caret caret = currentComponent.getCaret();
/*if (!caret.isSelectionVisible()) {
if (!find(-1)) { // nothing found
return false;
}
}*/
// now there's selected text to be replaced
BaseDocument doc = (BaseDocument)currentComponent.getDocument();
/*int startPos = currentComponent.getSelectionStart();
int len = currentComponent.getSelectionEnd() - startPos;*/
try {
doc.atomicLock();
if (len > 0) {
doc.remove(startPos, len);
}
if (replaceWith != null && replaceWith.length() > 0) {
doc.insertString(startPos, replaceWith, null);
}
} finally {
doc.atomicUnlock();
}
}
return true;
}
private boolean replaceInForm(RADComponent component, String propertyName,
ResourceBundleStringForm newValue) {
RADComponent.RADProperty property = component.getPropertyByName(propertyName);
try {
property.setCurrentEditor(new ResourceBundleStringFormEditor());
property.setValue(newValue);
return true;
}
catch (Exception e) {
// pending
TopManager.getDefault().notifyException(e);
return false;
}
}
/** Finds from the position (or the current position if position == -1,
* returns true if a hardcoded string was found
*/
public boolean find(int position) {
if (ensureComponentValid(position)) {
Utilities.clearStatusText(currentComponent);
Caret caret = currentComponent.getCaret();
BaseDocument doc = (BaseDocument)currentComponent.getDocument();
int dotPos = caret.getDot();
int[] ret = find(currentComponent, dotPos, -1);
if (ret != null) {
//System.out.println("find: returned [" + ret[0] + ", " + ret[1] + ", " +ret[2] + ", " +ret[3] + "]");
try {
getLocalizeInfo().setPosition(ret[0]);
getLocalizeInfo().setLength(ret[1]);
getLocalizeInfo().setHardString(extractString(doc.getText(ret[0], ret[1])));
getLocalizeInfo().setHardLine(doc.getText(ret[2], ret[3]));
getLocalizeInfo().setGuarded((doc instanceof GuardedDocument) &&
((GuardedDocument)doc).isPosGuarded(ret[0]));
}
catch (BadLocationException e) {
throw new InternalError();
}
caret.setDot(ret[0]);
caret.moveDot(ret[0] + ret[1]);
Utilities.setStatusText(currentComponent,
java.text.MessageFormat.format(NbBundle.getBundle(LocalizeSupport.class).
getString("CTL_HardStringFound"),
new Object[] {Utilities.debugPosition((BaseDocument)currentComponent.getDocument(), ret[0])}));
/*"Hardcoded string found at " +
Utilities.debugPosition((BaseDocument)currentComponent.getDocument(), ret[0]));*/
return true;
} else { // not found
Utilities.setStatusBoldText(currentComponent, NbBundle.getBundle(LocalizeSupport.class).
getString("CTL_HardStringNotFound"));
}
}
return false;
}
private String extractString(String sourceString) {
if ((sourceString.length() >= 2) &&
(sourceString.charAt(0) == '"') &&
(sourceString.charAt(sourceString.length() - 1) == '"'))
sourceString = sourceString.substring(1, sourceString.length() - 1);
StringBuffer result = new StringBuffer();
for (int i = 0; i < sourceString.length() ; i++) {
if ((sourceString.charAt(i) == '\\') && (i + 1 < sourceString.length()))
i++;
result.append(sourceString.charAt(i));
}
return result.toString();
}
/** Find the searched expression
* @startPos position where the start of the search will occur. It must
* be valid position greater or equal than zero
* @endPos position where the search will stop. -1 doesn't mean the end
* of document in this case but rather the default behavior which
* depends on the direction and wrapping.
* @return position and length of the text found provided in array
* containing these two ints; returns null if nothing is found
*/
public int[] find(JTextComponent c, int startPos, int endPos) {
boolean wrap = false;
BaseDocument doc = (BaseDocument)c.getDocument();
if (c != null) {
LocalizeFinder fnd = getLocalizeFinder(doc);
int pos = -1;
try {
int docLen = doc.getLength();
if (startPos == -1) {
startPos = docLen;
}
int restPatch = 2; // !!! quick infinite loop patch
while (true && restPatch-- > 0) {
int limitPos;
if (endPos == -1) { // invalid pos
limitPos = docLen;
} else {
if (startPos < endPos) {
limitPos = endPos;
} else {
limitPos = docLen;
}
}
pos = doc.find(fnd, startPos, limitPos);
//System.out.println("searched area [" + startPos + ", " + limitPos + "], docLen=" + docLen + ", pos=" + pos);
if (pos != -1) {
break;
}
if (wrap) {
if (endPos == -1) {
if (limitPos == docLen) {
startPos = 0;
} else {
break;
}
} else { // endPos != -1 (&& wrap)
if (limitPos == endPos) {
break;
}
startPos = 0;
}
} else { // no wrap set
break;
}
}
} catch (BadLocationException e) {
throw new Error(); // shouldn't happen
}
if (pos != -1) {
int[] ret = new int[4];
ret[0] = pos;
ret[1] = fnd.getFoundLength();
ret[2] = fnd.getLineStart();
ret[3] = fnd.getLineLength();
return ret;
}
}
return null;
}
/** Get localize finder */
public LocalizeFinder getLocalizeFinder(BaseDocument doc) {
LocalizeFinder localizeFinder = (LocalizeFinder)doc.getProperty(LOCALIZE_FINDER_PROP);
if (localizeFinder == null) {
localizeFinder = new LocalizeFinder();
doc.putProperty(LOCALIZE_FINDER_PROP, localizeFinder);
}
return localizeFinder;
}
private void showDialog() {
if (localizeDialog == null) {
localizePanel = new LocalizePanel();
final JButton[] buttons = localizePanel.getButtons();
localizeDialog = createLocalizeDialog(localizePanel, buttons, 0, 3,
new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (evt.getSource() == buttons[0])
doReplace();
if (evt.getSource() == buttons[1])
doReplaceAll();
if (evt.getSource() == buttons[2])
doSkip();
if (evt.getSource() == buttons[3])
doCancel();
}
}
);
localizeDialog.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
doCancel();
}
});
localizeDialog.setVisible(true);
}
}
private void hideDialog() {
if (localizeDialog != null) {
localizeDialog.setVisible(false);
localizeDialog.dispose();
localizeDialog = null;
}
}
private void fillDialogValues() {
localizePanel.setLocalizeInfo(getLocalizeInfo());
PropertiesDataObject pdo = (localizePanel.getValue() == null) ? null : localizePanel.getValue().getResourceBundle();
localizePanel.setValue(getLocalizeInfo().getDefaultBundleString(pdo));
}
private Dialog createLocalizeDialog(JPanel localizePanel, final JButton[] buttons,
final int defaultButtonIndex, final int cancelButtonIndex,
final ActionListener l) {
DialogDescriptor dd = new DialogDescriptor(localizePanel, NbBundle.getBundle(LocalizeSupport.class).
getString("CTL_LocalizeDialogTitle") + " " + obj.getName(), false, l);
dd.setOptionType(DialogDescriptor.DEFAULT_OPTION);
dd.setOptions(new Object[0]);
dd.setAdditionalOptions(new Object[0]);
dd.setHelpCtx (new HelpCtx (LocalizePanel.class));
// add listener to buttons
for (int i = 0; i < buttons.length; i++) {
buttons[i].addActionListener(l);
}
return TopManager.getDefault().createDialog(dd);
/*
JDialog d = new JDialog();
d.setTitle(NbBundle.getBundle(LocalizeSupport.class).getString("CTL_LocalizeDialogTitle") +
" " + obj.getName());
d.getContentPane().add(localizePanel, BorderLayout.CENTER);
// add listener to buttons
for (int i = 0; i < buttons.length; i++) {
buttons[i].addActionListener(l);
}
d.getRootPane().setDefaultButton(buttons[defaultButtonIndex]);
d.getRootPane().registerKeyboardAction(
new ActionListener() {
public void actionPerformed(ActionEvent evt) {
l.actionPerformed(
new ActionEvent(buttons[cancelButtonIndex], 0, null));
}
},
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true),
JComponent.WHEN_IN_FOCUSED_WINDOW
);
d.pack();
d.setLocation(100, 100);
return d;*/
}
/** Returns the RADComponent or null if not found. Must be called after initing componentName. */
private RADComponent getComponent(String compName) {
if (compName == null)
throw new IllegalStateException();
if ((compName.length() > 0) && (obj instanceof FormDataObject)) {
return ((FormDataObject)obj).getFormEditor().getFormManager().findRADComponent(compName);
}
else
return null;
}
/** Class providing information about localization parameters.
*/
public class LocalizeInfo {
private ResourceBundleStringEditor editor = new ResourceBundleStringEditor ();
private ResourceBundleStringFormEditor editorForm = new ResourceBundleStringFormEditor ();
/** Holds value of property hardString. */
private String hardString;
/** Holds value of property hardLine. */
private String hardLine;
/** Holds value of property guarded. */
private boolean guarded;
/** Holds the position where hardcored string was found. */
private int position;
/** Holds the lenght of the hardcored string. */
private int len;
private String componentName;
private String propertyName;
/** Getter for property hardString.
*@return Value of property hardString.
*/
public String getHardString() {
return hardString;
}
/** Setter for property hardString.
*@param hardString New value of property hardString.
*/
public void setHardString(String hardString) {
this.hardString = hardString;
componentName = null;
propertyName = null;
}
/** Getter for property hardLine.
*@return Value of property hardLine.
*/
public String getHardLine() {
return hardLine;
}
/** Setter for property hardLine.
*@param hardLine New value of property hardLine.
*/
public void setHardLine(String hardLine) {
this.hardLine = hardLine;
componentName = null;
propertyName = null;
}
/** Getter for property position.
*@return Value of property position.
*/
public int getPosition() {
return position;
}
/** Setter for property position.
*@param position New value of property position.
*/
public void setPosition(int position) {
this.position = position;
}
/** Getter for property lenght.
*@return Value of property length.
*/
public int getLength() {
return len;
}
/** Setter for property length.
*@param position New value of property length.
*/
public void setLength(int len) {
this.len = len;
}
/** Getter for property guarded.
*@return Value of property guarded.
*/
public boolean isGuarded() {
return guarded;
}
/** Setter for property guarded.
*@param guarded New value of property guarded.
*/
public void setGuarded(boolean guarded) {
this.guarded = guarded;
}
/** Getter for property componentName.
*@return Value of property componentName.
*/
public String getComponentName() {
if (componentName == null)
analyzeForm();
return componentName;
}
/** Getter for property propertyName.
*@return Value of property propertyName.
*/
public String getPropertyName() {
if (propertyName == null)
analyzeForm();
return propertyName;
}
/** Gets the default bundle string given a PropertiesDataObject. May return null if the value
* can not be replaced (i.e. is in a guraded block and a form property has not been found). */
public ResourceBundleString getDefaultBundleString(PropertiesDataObject pdo) {
if (isGuarded()) {
// guarded block
if (getComponentName().length() > 0) {
// form
ResourceBundleStringForm str = new ResourceBundleStringForm();
str.setResourceBundle(pdo);
str.setDefaultValue(getHardString());
RADComponent rcomp = getComponent(componentName);
RADComponent.RADProperty rprop = rcomp.getPropertyByName (propertyName);
editorForm.setRADComponent(rcomp, rprop);
editorForm.setValue(str);
return (ResourceBundleStringForm)editorForm.getValue();
}
else {
// not found
InvalidResourceBundleString str = new InvalidResourceBundleString();
str.setResourceBundle(pdo);
return str;
}
}
else {
// no form
ResourceBundleString str;
if (pdo == null) {
editor.setValue(null);
str = (ResourceBundleString)editor.getValue();
}
else {
str = new ResourceBundleString();
str.setResourceBundle(pdo);
}
putDefaultStringKey(str);
str.setDefaultValue(getHardString());
return str;
}
}
private void putDefaultStringKey(ResourceBundleString str) {
String baseKey = Util.stringToKey(hardString);
int index = 0;
str.setKey(baseKey);
while (str.getPropertyValue() != null) {
index ++;
str.setKey(baseKey + "." + index);
}
}
/** Analyzes a line of text in a guraded block, tries to find the name
* of the component and of the property, so it can be changed via FormEditor API
*/
private void analyzeForm() {
componentName = "";
propertyName = "";
// must be guarded
if (!isGuarded())
return;
// must be a form
if (!(obj instanceof FormDataObject))
return;
int firstDot = hardLine.indexOf('.');
// must contain a dot
if (firstDot == -1)
return;
String compName = hardLine.substring(0, firstDot).trim();
// find the component name
RADComponent comp = getComponent(compName);
// must be a valid component
if (comp == null)
return;
int leftPar = hardLine.indexOf('(', firstDot);
// must contain ( after .
if (leftPar == -1)
return;
String setterName = hardLine.substring(firstDot + 1, leftPar).trim();
// must be a setter
if (!setterName.startsWith("set"))
return;
String propName = Introspector.decapitalize(setterName.substring("set".length()));
RADComponent.RADProperty property = comp.getPropertyByName(propName);
if (property == null)
return;
// now it's fine
componentName = compName;
propertyName = propName;
}
}
/** ResourceBundleString representing invalid value. */
public static class InvalidResourceBundleString extends ResourceBundleString {
static final long serialVersionUID =8135168688193465968L;
public InvalidResourceBundleString() {
}
}
}
/*
* <<Log>>
* 13 Gandalf-post-FCS1.11.1.0 3/8/00 Miloslav Metelka StyledGuardedDocument ->
* GuardedDocument
* 12 Gandalf 1.11 11/27/99 Patrik Knakal
* 11 Gandalf 1.10 11/5/99 Jesse Glick Context help jumbo
* patch.
* 10 Gandalf 1.9 10/25/99 Petr Jiricka Various bugfixes
* 9 Gandalf 1.8 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 8 Gandalf 1.7 10/15/99 Petr Jiricka Fixed bug - after
* replace in guarded block the cursor jumps at the end of the guarded
* block.
* 7 Gandalf 1.6 10/13/99 Petr Jiricka Debug messages removed
* 6 Gandalf 1.5 10/12/99 Petr Jiricka Form eidtor component
* search fixed
* 5 Gandalf 1.4 10/10/99 Petr Hamernik console debug messages
* removed.
* 4 Gandalf 1.3 9/13/99 Petr Jiricka Reflects changes in
* Editor module
* 3 Gandalf 1.2 9/13/99 Ian Formanek FormAwareEditor.setRADComponent
* change
* 2 Gandalf 1.1 9/8/99 Petr Jiricka
* 1 Gandalf 1.0 9/2/99 Petr Jiricka
* $
*/